﻿using System;
using System.Globalization;
using System.IO;
using System.Xml;
using ECM7.NantContrib.Types;
using NAnt.Core;
using NAnt.Core.Attributes;

namespace ECM7.NantContrib.Tasks.Xml
{
	/// <summary>
	/// Конфигурирование приложения
	/// </summary>
	[TaskName("configure")]
	public class ConfigureTask : Task
	{
		#region Parameters

		/// <summary>
		/// Путь к конфигурационному файлу приложения
		/// </summary>
		[TaskAttribute("file", Required = true)]
		public virtual FileInfo XmlFile { get; set; }

		/// <summary>
		/// Обрабатывать пустые символы
		/// </summary>
		[TaskAttribute("preserveWhitespace", Required = false), BooleanValidator]
		public bool PreserveWhitespace { get; set; }

		/// <summary>
		/// Правила конфигурации
		/// </summary>
		[BuildElement("rules", Required = true)]
		public virtual RulesCollection Rules { get; set; }

		#endregion

		#region Overrides of Task

		/// <summary>
		/// Выполнение необходимых действий
		/// </summary>
		protected override void ExecuteTask()
		{
			if (!XmlFile.Exists)
			{
				string message = string.Format(CultureInfo.InvariantCulture, "Не найден обрабатываемый файл: {0}", XmlFile.FullName);
				throw new BuildException(message, Location);
			}

			try
			{
				XmlDocument document = LoadDocument(XmlFile.FullName, PreserveWhitespace);

				foreach (var rule in Rules.Rules)
				{
					XmlNodeList nodes = SelectNodes(rule.Key, document);
					if (nodes != null && nodes.Count > 0)
					{
						UpdateNodes(nodes, rule.Value);
					}
				}

				SaveDocument(document, XmlFile.FullName);
			}
			catch (BuildException)
			{
				throw;
			}
			catch (Exception ex)
			{
				string message = string.Format(CultureInfo.InvariantCulture, "Не удалось обработать файл: {0}", XmlFile.FullName);
				throw new BuildException(message, Location, ex);
			}
		}

		/// <summary>
		/// Сохранение конфигурационного файла
		/// </summary>
		/// <param name="document">Сохраняемые данные</param>
		/// <param name="fileName">Имя файла</param>
		private void SaveDocument(XmlDocument document, string fileName)
		{
			try
			{
				Log(Level.Verbose, "Попытка сохранения XML-документа '{0}'.", fileName);
				document.Save(fileName);
				Log(Level.Verbose, "XML-документ '{0}' успешно сохранен.", fileName);
			}
			catch (Exception exception)
			{
				string message = string.Format(CultureInfo.InvariantCulture, "Не удалось сохранить XML-документ '{0}'", fileName);
				throw new BuildException(message, Location, exception);
			}
		}

		/// <summary>
		/// Установка нужных значений параметрам конфигурационного файла
		/// </summary>
		/// <param name="nodes">Элементы конфигурационного файла</param>
		/// <param name="value">Значение</param>
		private void UpdateNodes(XmlNodeList nodes, string value)
		{
			Log(Level.Verbose, "Установка элементам значения '{0}'.", value);

			int num = 0;
			foreach (XmlNode node in nodes)
			{
				Log(Level.Verbose, "Изменение элемента '{0}'.", num);
				node.InnerXml = value;
				num++;
			}

			Log(Level.Verbose, "Значения элементов успешно изменены.");
		}

		/// <summary>
		/// Поиск элементов, удовлетворяющих правилу
		/// </summary>
		/// <param name="xpath">Выражение XPath для поиска элементов</param>
		/// <param name="document">Документ, в котором производится поиск</param>
		private XmlNodeList SelectNodes(string xpath, XmlDocument document)
		{
			try
			{
				Log(Level.Verbose, "Поиск элементов по выражению XPath '{0}'.", xpath);
				XmlNodeList list = document.SelectNodes(xpath);
				if (list != null)
				{
					if (list.Count > 0)
					{
						Log(Level.Info, "Найдено для выражения '{0} найдено элементов: {1}.", xpath, list.Count);
					}
					else
					{
						Log(Level.Warning, "Не найдены элементы, соответствующие выражению '{0}'.", xpath);
					}
				}

				return list;
			}
			catch (Exception exception)
			{
				string message = string.Format(
					CultureInfo.InvariantCulture, "Не удалось выбрать элементы, соответствующие выражению '{0}'", xpath);

				throw new BuildException(message, Location, exception);
			}
		}

		/// <summary>
		/// Загрузка конфигурационного файла в память
		/// </summary>
		/// <param name="fileName">Имя файла</param>
		/// <param name="preserveWhitespace">Обрабатывать пустые символы</param>
		private XmlDocument LoadDocument(string fileName, bool preserveWhitespace)
		{
			try
			{
				Log(Level.Verbose, "Попытка загрузить XML-документ из файла '{0}'.", fileName);
				XmlDocument document = new XmlDocument { PreserveWhitespace = preserveWhitespace };
				document.Load(fileName);
				Log(Level.Verbose, "XML-документ из файла '{0}' загружен успешно.", fileName);
				return document;
			}
			catch (Exception exception)
			{
				string message = string.Format(CultureInfo.InvariantCulture, "Не удалось загрузить XML-документ {0}", fileName);
				throw new BuildException(message, Location, exception);
			}
		}

		#endregion
	}
}
